Skip to content

feat: claude effort controls, ultrathink UI, and adapter robustness#1146

Open
JustYannicc wants to merge 7 commits intopingdotgg:codething/648ca884-claudefrom
JustYannicc:codex/pr-179-no-teams
Open

feat: claude effort controls, ultrathink UI, and adapter robustness#1146
JustYannicc wants to merge 7 commits intopingdotgg:codething/648ca884-claudefrom
JustYannicc:codex/pr-179-no-teams

Conversation

@JustYannicc
Copy link

@JustYannicc JustYannicc commented Mar 16, 2026

What Changed

Claude Effort Controls & Ultrathink

  • Added ClaudeCodeEffort type with levels: low, medium, high, max, ultrathink
  • getEffectiveClaudeCodeEffort() maps effort levels to Claude SDK values
  • applyClaudePromptEffortPrefix() prepends "Ultrathink:" prefix to prompts
  • Generalized CodexTraitsPicker into a provider-aware traits picker with Claude effort labels
  • Ultrathink composer chrome: animated rainbow border, chroma pill badge, model picker glow effect

Adapter Robustness (Background Agents & Subagents)

  • Synthetic turn auto-start: Assistant messages arriving without an active turn (background agent/subagent responses between user prompts) now auto-start a synthetic turn so the response is visible in the UI
  • Stale turn cleanup: Auto-close orphaned synthetic turns in sendTurn to prevent session deadlock
  • Fragment leak prevention: Clear inFlightTools map in completeTurn to prevent stale JSON fragments from corrupting subsequent turns

Subagent Progress Surfacing

  • Claude task.progress events surface as activities with summary and lastToolName
  • collab_agent_tool_call classification for Agent/Task tools with "Subagent task" labels
  • Tests for tool classification and progress forwarding

Provider Health

  • Refreshed health check status probes with Fiber-based concurrent execution

Why

Background agent results, subagent responses, and tool call details were silently dropped because the adapter's turn state machine only handled user-initiated turns. When a background agent finished work and the lead generated a response, that response was invisible in the UI.

The effort controls bring feature parity with the Codex reasoning effort UI, adapted for Claude's effort model including the ultrathink mode.

UI Changes

  • Ultrathink mode shows an animated rainbow border around the composer and a chroma-animated "Ultrathink" pill badge
  • Effort dropdown shows Claude-specific labels (Low / Medium / High / Max / Ultrathink)
  • Provider model picker shows chroma animation when ultrathink is active

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • Type checks clean across web, server, and contracts
  • All 35 existing tests pass

Note

Add Claude effort controls, ultrathink UI, and adapter session robustness

  • Adds ClaudeCodeEffort options (low/medium/high/max/ultrathink) to contracts, shared model utilities, and the Claude adapter; effort is forwarded through session start and persisted per thread so changes trigger session restarts.
  • Prefixes outgoing prompts with Ultrathink:\n when ultrathink effort is selected, both on the client in ChatView and server-side in ClaudeAdapter.
  • Adds animated ultrathink UI: rainbow frame, chroma icon shift, and an 'Ultrathink' pill in the composer when the effort is active.
  • Generalizes CodexTraitsPicker into ProviderTraitsPicker with provider-aware effort labels and hides the Fast Mode toggle for non-Codex providers.
  • ClaudeAdapter.sendTurn now auto-completes a stale active turn instead of returning a validation error, and recovered sessions restore persisted modelOptions.

Macroscope summarized f882fc9.

@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 00acac78-a8ec-420a-8fbb-035ee3e6e99b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.

Change the reviews.profile setting to assertive to make CodeRabbit's nitpick more issues in your PRs.

@github-actions github-actions bot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Mar 16, 2026
@JustYannicc JustYannicc reopened this Mar 16, 2026
@JustYannicc JustYannicc marked this pull request as draft March 16, 2026 19:55
@JustYannicc JustYannicc changed the base branch from main to codething/648ca884-claude March 16, 2026 19:57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium

yield* upsertSessionBinding(resumed, input.binding.threadId);

In recoverSessionForThread, upsertSessionBinding is called at line 247 without passing persistedModelOptions and persistedProviderOptions, even though these values were extracted and used to start the session. After recovery, the runtime payload drops these options, so subsequent operations that read the persisted binding won't see the original model/provider configuration.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/ProviderService.ts around line 247:

In `recoverSessionForThread`, `upsertSessionBinding` is called at line 247 without passing `persistedModelOptions` and `persistedProviderOptions`, even though these values were extracted and used to start the session. After recovery, the runtime payload drops these options, so subsequent operations that read the persisted binding won't see the original model/provider configuration.

Evidence trail:
- apps/server/src/provider/Layers/ProviderService.ts lines 229-230 (REVIEWED_COMMIT): `persistedModelOptions` and `persistedProviderOptions` are extracted from `input.binding.runtimePayload`
- apps/server/src/provider/Layers/ProviderService.ts lines 235-236 (REVIEWED_COMMIT): These values are passed to `adapter.startSession()`
- apps/server/src/provider/Layers/ProviderService.ts line 247 (REVIEWED_COMMIT): `upsertSessionBinding(resumed, input.binding.threadId)` - called WITHOUT `extra` parameter
- apps/server/src/provider/Layers/ProviderService.ts lines 162-174 (REVIEWED_COMMIT): `upsertSessionBinding` signature shows optional `extra` parameter with modelOptions/providerOptions
- apps/server/src/provider/Layers/ProviderService.ts lines 89-101 (REVIEWED_COMMIT): `toRuntimePayloadFromSession` only includes modelOptions/providerOptions if provided in `extra` (lines 98-99)
- apps/server/src/provider/Layers/ProviderService.ts lines 308-311 (REVIEWED_COMMIT): Correct usage in `startSession` passes the extra parameter

@JustYannicc JustYannicc force-pushed the codex/pr-179-no-teams branch from abb5d9c to 2439fca Compare March 16, 2026 20:17
@JustYannicc JustYannicc changed the title fix: improve claude adapter robustness for background agents feat: claude effort controls, ultrathink UI, and adapter robustness Mar 16, 2026
@JustYannicc JustYannicc marked this pull request as ready for review March 16, 2026 20:33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High Layers/ClaudeCodeAdapter.ts:816

In completeTurn, context.inFlightTools.clear() is called at line 818 before iterating over context.inFlightTools.entries() at line 843. Since the map is cleared first, the loop never executes and in-flight tools are silently dropped without emitting item.completed events. The clear() should be moved after the loop or removed, since the loop already deletes each tool after processing.

    const completeTurn = (
      context: ClaudeSessionContext,
      status: ProviderRuntimeTurnStatus,
      errorMessage?: string,
      result?: SDKResultMessage,
    ): Effect.Effect<void> =>
      Effect.gen(function* () {
-        // Clear any stale in-flight tools from interrupted content blocks
-        context.inFlightTools.clear();
         const turnState = context.turnState;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/ClaudeCodeAdapter.ts around lines 816-826:

In `completeTurn`, `context.inFlightTools.clear()` is called at line 818 before iterating over `context.inFlightTools.entries()` at line 843. Since the map is cleared first, the loop never executes and in-flight tools are silently dropped without emitting `item.completed` events. The `clear()` should be moved after the loop or removed, since the loop already deletes each tool after processing.

Evidence trail:
apps/server/src/provider/Layers/ClaudeCodeAdapter.ts lines 810-860 at REVIEWED_COMMIT: Line 818 shows `context.inFlightTools.clear();` and line 843 shows `for (const [index, tool] of context.inFlightTools.entries())`. No code between these lines repopulates the map, confirming the loop operates on an already-cleared map.

@juliusmarminge
Copy link
Member

had to do some sweeping changes to align with anthropic branding guidelines. can you resolve the conflicts?

@JustYannicc
Copy link
Author

yes working on it

JustYannicc and others added 7 commits March 16, 2026 23:54
- Auto-start synthetic turns for assistant messages arriving without an
  active turnState (fixes invisible background agent/subagent responses
  that arrive between user-initiated turns)
- Auto-close stale synthetic turns in sendTurn to prevent session deadlock
  when a background response left an orphaned turn open
- Clear inFlightTools between turns in completeTurn to prevent stale JSON
  fragments from corrupting the next turn's tool inputs
- Fix missing Fiber import in ProviderHealth

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The clear() was placed before the loop that emits item.completed events
for remaining in-flight tools, causing the loop to never execute. Move
it after the loop so tools get properly finalized before cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Updates all remaining references from "claudeCode" to "claudeAgent"
to match the Anthropic branding guidelines applied upstream. Includes
provider kind literals, object property keys, test payloads, and
component comparisons across server, web, and shared packages.

Also fixes synthetic turn state to match upstream's refactored
ClaudeTurnState interface (assistantTextBlocks map instead of the
old assistantItemId/messageCompleted/emittedTextDelta fields).

Also fixes inFlightTools.clear() placement: moved after the loop
that emits item.completed events for remaining in-flight tools,
so tools are properly finalized before cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JustYannicc JustYannicc force-pushed the codex/pr-179-no-teams branch from 2439fca to f882fc9 Compare March 16, 2026 23:08
@JustYannicc
Copy link
Author

@juliusmarminge done. you should be able to merge it no problem now.

<svg {...props} preserveAspectRatio="xMidYMid" viewBox="0 0 256 257">
<path
fill="#D97757"
fill="currentColor"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

status.provider === "codex"
? "Codex"
: status.provider === "claudeAgent"
? "Claude Code"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not allowed to use the term 'Claude Code'

MenuTrigger,
} from "../ui/menu";

export const CodexTraitsPicker = memo(function CodexTraitsPicker(props: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm not sure it's worth compacting these as one just yet... we don't really know what the future will look like so keeping them separate for now is the best. iirc i separated them when i had all codex/claude/cursor working because they had differernt enough logic to justify separate components instead of single component with switch statements internalyl

const TaskProgressPayload = Schema.Struct({
taskId: RuntimeTaskId,
description: TrimmedNonEmptyStringSchema,
summary: Schema.optional(TrimmedNonEmptyStringSchema),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this cover that "description" doesn't? is it something claude specific? not opposed to keeping mostly curious

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants